/*
 * Copyright (C) 2007 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.dx.rop.code;

import com.android.dx.rop.type.StdTypeList;
import com.android.dx.rop.type.Type;
import com.android.dx.rop.type.TypeList;
import com.android.dx.util.ToHuman;

/**
 * A register-based instruction. An instruction is the combination of an opcode
 * (which specifies operation and source/result types), a list of actual sources
 * and result registers/values, and additional information.
 */
public abstract class Insn implements ToHuman {

	/** {@code non-null;} opcode */
	private final Rop opcode;

	/** {@code non-null;} source position */
	private final SourcePosition position;

	/** {@code null-ok;} spec for the result of this instruction, if any */
	private final RegisterSpec result;

	/** {@code non-null;} specs for all the sources of this instruction */
	private final RegisterSpecList sources;

	/**
	 * Constructs an instance.
	 * 
	 * @param opcode
	 *            {@code non-null;} the opcode
	 * @param position
	 *            {@code non-null;} source position
	 * @param result
	 *            {@code null-ok;} spec for the result, if any
	 * @param sources
	 *            {@code non-null;} specs for all the sources
	 */
	public Insn(Rop opcode, SourcePosition position, RegisterSpec result,
			RegisterSpecList sources) {
		if (opcode == null) {
			throw new NullPointerException("opcode == null");
		}

		if (position == null) {
			throw new NullPointerException("position == null");
		}

		if (sources == null) {
			throw new NullPointerException("sources == null");
		}

		this.opcode = opcode;
		this.position = position;
		this.result = result;
		this.sources = sources;
	}

	/**
	 * {@inheritDoc} Instances of this class compare by identity. That is,
	 * {@code x.equals(y)} is only true if {@code x == y}.
	 */
	@Override
	public final boolean equals(Object other) {
		return (this == other);
	}

	/**
	 * {@inheritDoc} This implementation returns the identity hashcode of this
	 * instance. This is proper, since instances of this class compare by
	 * identity (see {@link #equals}).
	 */
	@Override
	public final int hashCode() {
		return System.identityHashCode(this);
	}

	/** {@inheritDoc} */
	@Override
	public String toString() {
		return toStringWithInline(getInlineString());
	}

	/**
	 * Gets a human-oriented (and slightly lossy) string for this instance.
	 * 
	 * @return {@code non-null;} the human string form
	 */
	public String toHuman() {
		return toHumanWithInline(getInlineString());
	}

	/**
	 * Gets an "inline" string portion for toHuman(), if available. This is the
	 * portion that appears after the Rop opcode
	 * 
	 * @return {@code null-ok;} if non-null, the inline text for toHuman()
	 */
	public String getInlineString() {
		return null;
	}

	/**
	 * Gets the opcode.
	 * 
	 * @return {@code non-null;} the opcode
	 */
	public final Rop getOpcode() {
		return opcode;
	}

	/**
	 * Gets the source position.
	 * 
	 * @return {@code non-null;} the source position
	 */
	public final SourcePosition getPosition() {
		return position;
	}

	/**
	 * Gets the result spec, if any. A return value of {@code null} means this
	 * instruction returns nothing.
	 * 
	 * @return {@code null-ok;} the result spec, if any
	 */
	public final RegisterSpec getResult() {
		return result;
	}

	/**
	 * Gets the spec of a local variable assignment that occurs at this
	 * instruction, or null if no local variable assignment occurs. This may be
	 * the result register, or for {@code mark-local} insns it may be the
	 * source.
	 * 
	 * @return {@code null-ok;} a named register spec or null
	 */
	public final RegisterSpec getLocalAssignment() {
		RegisterSpec assignment;
		if (opcode.getOpcode() == RegOps.MARK_LOCAL) {
			assignment = sources.get(0);
		} else {
			assignment = result;
		}

		if (assignment == null) {
			return null;
		}

		LocalItem localItem = assignment.getLocalItem();

		if (localItem == null) {
			return null;
		}

		return assignment;
	}

	/**
	 * Gets the source specs.
	 * 
	 * @return {@code non-null;} the source specs
	 */
	public final RegisterSpecList getSources() {
		return sources;
	}

	/**
	 * Gets whether this instruction can possibly throw an exception. This is
	 * just a convenient wrapper for {@code getOpcode().canThrow()}.
	 * 
	 * @return {@code true} iff this instruction can possibly throw
	 */
	public final boolean canThrow() {
		return opcode.canThrow();
	}

	/**
	 * Gets the list of possibly-caught exceptions. This returns
	 * {@link StdTypeList#EMPTY} if this instruction has no handlers, which can
	 * be <i>either</i> if this instruction can't possibly throw or if it merely
	 * doesn't handle any of its possible exceptions. To determine whether this
	 * instruction can throw, use {@link #canThrow}.
	 * 
	 * @return {@code non-null;} the catches list
	 */
	public abstract TypeList getCatches();

	/**
	 * Calls the appropriate method on the given visitor, depending on the class
	 * of this instance. Subclasses must override this.
	 * 
	 * @param visitor
	 *            {@code non-null;} the visitor to call on
	 */
	public abstract void accept(Visitor visitor);

	/**
	 * Returns an instance that is just like this one, except that it has a
	 * catch list with the given item appended to the end. This method throws an
	 * exception if this instance can't possibly throw. To determine whether
	 * this instruction can throw, use {@link #canThrow}.
	 * 
	 * @param type
	 *            {@code non-null;} type to append to the catch list
	 * @return {@code non-null;} an appropriately-constructed instance
	 */
	public abstract Insn withAddedCatch(Type type);

	/**
	 * Returns an instance that is just like this one, except that all register
	 * references have been offset by the given delta.
	 * 
	 * @param delta
	 *            the amount to offset register references by
	 * @return {@code non-null;} an appropriately-constructed instance
	 */
	public abstract Insn withRegisterOffset(int delta);

	/**
	 * Returns an instance that is just like this one, except that, if possible,
	 * the insn is converted into a version in which a source (if it is a
	 * constant) is represented directly rather than as a register reference.
	 * {@code this} is returned in cases where the translation is not possible.
	 * 
	 * @return {@code non-null;} an appropriately-constructed instance
	 */
	public Insn withSourceLiteral() {
		return this;
	}

	/**
	 * Returns an exact copy of this Insn
	 * 
	 * @return {@code non-null;} an appropriately-constructed instance
	 */
	public Insn copy() {
		return withRegisterOffset(0);
	}

	/**
	 * Compares, handling nulls safely
	 * 
	 * @param a
	 *            first object
	 * @param b
	 *            second object
	 * @return true if they're equal or both null.
	 */
	private static boolean equalsHandleNulls(Object a, Object b) {
		return (a == b) || ((a != null) && a.equals(b));
	}

	/**
	 * Compares Insn contents, since {@code Insn.equals()} is defined to be an
	 * identity compare. Insn's are {@code contentEquals()} if they have the
	 * same opcode, registers, source position, and other metadata.
	 * 
	 * @return true in the case described above
	 */
	public boolean contentEquals(Insn b) {
		return opcode == b.getOpcode() && position.equals(b.getPosition())
				&& (getClass() == b.getClass())
				&& equalsHandleNulls(result, b.getResult())
				&& equalsHandleNulls(sources, b.getSources())
				&& StdTypeList.equalContents(getCatches(), b.getCatches());
	}

	/**
	 * Returns an instance that is just like this one, except with new result
	 * and source registers.
	 * 
	 * @param result
	 *            {@code null-ok;} new result register
	 * @param sources
	 *            {@code non-null;} new sources registers
	 * @return {@code non-null;} an appropriately-constructed instance
	 */
	public abstract Insn withNewRegisters(RegisterSpec result,
			RegisterSpecList sources);

	/**
	 * Returns the string form of this instance, with the given bit added in the
	 * standard location for an inline argument.
	 * 
	 * @param extra
	 *            {@code null-ok;} the inline argument string
	 * @return {@code non-null;} the string form
	 */
	protected final String toStringWithInline(String extra) {
		StringBuffer sb = new StringBuffer(80);

		sb.append("Insn{");
		sb.append(position);
		sb.append(' ');
		sb.append(opcode);

		if (extra != null) {
			sb.append(' ');
			sb.append(extra);
		}

		sb.append(" :: ");

		if (result != null) {
			sb.append(result);
			sb.append(" <- ");
		}

		sb.append(sources);
		sb.append('}');

		return sb.toString();
	}

	/**
	 * Returns the human string form of this instance, with the given bit added
	 * in the standard location for an inline argument.
	 * 
	 * @param extra
	 *            {@code null-ok;} the inline argument string
	 * @return {@code non-null;} the human string form
	 */
	protected final String toHumanWithInline(String extra) {
		StringBuffer sb = new StringBuffer(80);

		sb.append(position);
		sb.append(": ");
		sb.append(opcode.getNickname());

		if (extra != null) {
			sb.append("(");
			sb.append(extra);
			sb.append(")");
		}

		if (result == null) {
			sb.append(" .");
		} else {
			sb.append(" ");
			sb.append(result.toHuman());
		}

		sb.append(" <-");

		int sz = sources.size();
		if (sz == 0) {
			sb.append(" .");
		} else {
			for (int i = 0; i < sz; i++) {
				sb.append(" ");
				sb.append(sources.get(i).toHuman());
			}
		}

		return sb.toString();
	}

	/**
	 * Visitor interface for this (outer) class.
	 */
	public static interface Visitor {

		/**
		 * Visits a {@link PlainInsn}.
		 * 
		 * @param insn
		 *            {@code non-null;} the instruction to visit
		 */
		public void visitPlainInsn(PlainInsn insn);

		/**
		 * Visits a {@link PlainCstInsn}.
		 * 
		 * @param insn
		 *            {@code non-null;} the instruction to visit
		 */
		public void visitPlainCstInsn(PlainCstInsn insn);

		/**
		 * Visits a {@link SwitchInsn}.
		 * 
		 * @param insn
		 *            {@code non-null;} the instruction to visit
		 */
		public void visitSwitchInsn(SwitchInsn insn);

		/**
		 * Visits a {@link ThrowingCstInsn}.
		 * 
		 * @param insn
		 *            {@code non-null;} the instruction to visit
		 */
		public void visitThrowingCstInsn(ThrowingCstInsn insn);

		/**
		 * Visits a {@link ThrowingInsn}.
		 * 
		 * @param insn
		 *            {@code non-null;} the instruction to visit
		 */
		public void visitThrowingInsn(ThrowingInsn insn);

		/**
		 * Visits a {@link FillArrayDataInsn}.
		 * 
		 * @param insn
		 *            {@code non-null;} the instruction to visit
		 */
		public void visitFillArrayDataInsn(FillArrayDataInsn insn);
	}

	/**
	 * Base implementation of {@link Visitor}, which has empty method bodies for
	 * all methods.
	 */
	public static class BaseVisitor implements Visitor {

		/** {@inheritDoc} */
		public void visitPlainInsn(PlainInsn insn) {
			// This space intentionally left blank.
		}

		/** {@inheritDoc} */
		public void visitPlainCstInsn(PlainCstInsn insn) {
			// This space intentionally left blank.
		}

		/** {@inheritDoc} */
		public void visitSwitchInsn(SwitchInsn insn) {
			// This space intentionally left blank.
		}

		/** {@inheritDoc} */
		public void visitThrowingCstInsn(ThrowingCstInsn insn) {
			// This space intentionally left blank.
		}

		/** {@inheritDoc} */
		public void visitThrowingInsn(ThrowingInsn insn) {
			// This space intentionally left blank.
		}

		/** {@inheritDoc} */
		public void visitFillArrayDataInsn(FillArrayDataInsn insn) {
			// This space intentionally left blank.
		}
	}
}
